import shelve
from datetime import datetime

class ModifiedBuffer(object):
    def __init__(self, filename, **kw):
        self.filename = filename
        self.__dict__.update(kw)



## this one uses pickle

class BuffersDb(object):
    def __init__(self, filename):
        self.storage = shelve.open(filename)
        self.filename = filename

    def set(self, filename, **metadata):
        buff = ModifiedBuffer(filename, when=datetime.now(), **metadata)
        self[filename] = buff

    def __setitem__(self, filename, buff):
        self.storage[filename] = buff
        
    def __delitem__(self, filename):
        del self.storage[filename]

    def __getitem__(self, filename):
        return self.storage[filename]

    def vacuum(self):
        """Remove files that didn't need this!"""
        files = [buff for fname, buff in self.storage]
        files.sort(key="when")
        for buff in files[10:]:
            del self.storage[buff.filename]
            
    def flush(self):
        self.storage.close()
        self.storage = shelve.open(self.filename)

    def __del__(self):
        self.storage.close()


import gtk

def _move_mark_from_offset(buff, mark_name, offset):
    txt_iter = buff.get_iter_at_offset(offset)
    buff.move_mark_by_name(mark_name, txt_iter)

def _offset_from_mark(buff, mark_name):
    txt_iter = buff.get_iter_at_mark(buff.get_mark(mark_name))
    return txt_iter.get_offset()

class GtkBuffersDb(object):
    """
    GtkBuffersDb, accepts gtk.TextBuffer compatible objects instead of
    raw strings
    """
    
    def __init__(self, filename, factory=gtk.TextBuffer):
        self.factory = factory
        self.db = BuffersDb(filename)
        
    def __getitem__(self, filename):
        """Get a TextBuffer from a filename"""
        buff = self.factory()
        buff.set_text(open(filename).read())
        buff.set_modified(False)
        
        try:
            item = self.db[filename]
            # try to load the old state back
            buff.set_text(item.text)
            _move_mark_from_offset(buff, "insert", item.insert)
            _move_mark_from_offset(buff, "selection_bound",
                                   item.selection_bound)
        except KeyError:
            pass
        return buff

    def __setitem__(self, filename, buff):
        """Sets the filename contents from a TextBuffer"""
        self.db.set(
            filename,
            text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()),
            insert = _offset_from_mark(buff, "insert"),
            selection_bound = _offset_from_mark(buff, "selection_bound"),
        )
        


################
# Make widgets persistent
## import gobject

## is_param_rw = lambda x: (x.flags & (gobject.PARAM_WRITABLE | gobject.PARAM_READABLE))

## identity = lambda x: x

## ADAPTERS = {
##     gobject.TYPE_ENUM: int,
##     }

## for g_type in (gobject.TYPE_INT, gobject.TYPE_UINT, gobject.TYPE_UINT64,
##                gobject.TYPE_ULONG, gobject.TYPE_UNICHAR, gobject.TYPE_STRING,
##                gobject.TYPE_PYOBJECT, gobject.TYPE_NONE, gobject.TYPE_LONG,
##                gobject.TYPE_INT64, gobject.TYPE_GSTRING, gobject.TYPE_FLOAT,
##                gobject.TYPE_DOUBLE, gobject.TYPE_CHAR, gobject.TYPE_BOOLEAN):
##     ADAPTERS[g_type] = identity
               



## def serialize_gobject(obj):
##     props = [prop for prop in gobject.list_properties(obj) \
##              if is_param_rw(prop)]
    
##     values = {}
##     for prop in props:
##         try:
##             adapter = ADAPTERS[prop.value_type]
##             name = prop.name
##             values[name] = adapter(obj.get_property(name))
##         except KeyError:
##             pass
##     return values

## def unserialize_gobject(obj, data):
##     for key, value in data.items():
##         try:
##             obj.set_property(key, value)
##         except TypeError:
##             pass

# TODO: serialize the scroll and cursor position
from gtk import gdk
def get_current_desktop():
    root = gdk.get_default_root_window()
    data = root.property_get("_NET_CURRENT_DESKTOP")
    if data is None:
        return
    return data[-1][0]

def _get_gdk_window(window):
    if window.window is None:
        window.realize()
    return window.window

def get_window_desktop(window):
    window = _get_gdk_window(window)
    data = window.property_get("_NET_WM_DESKTOP")
    if data is None:
        return
    return data[-1][0]

def move_to_desktop(window, desktop):
    w = window
    window = _get_gdk_window(window)
    window.property_change(
        "_NET_WM_DESKTOP",
        "CARDINAL",
        32,
        gdk.PROP_MODE_REPLACE,
        [desktop])
    w.present()
    
def serialize_window(win, storage, key="gui:/"):
    data = {
        "size": win.get_size(),
        "position": win.get_position(),
        }
    storage[key] = data
    
def unserialize_window(win, storage, key="gui:/"):
    try:
        data = storage[key]
        win.resize(*data["size"])
        win.move(*data["position"])
    except KeyError:
        pass

def scroll_to_cursor(tv):
    buff = tv.get_buffer()
    insert = buff.get_iter_at_mark(buff.get_insert())
    tv.scroll_to_iter(insert, 0.25)

########
# Now onto the example
import gobject

def main(filename):
    db = GtkBuffersDb("/tmp/buffers.db")
    win = gtk.Window()
    unserialize_window(win, db.db.storage)
    port = gtk.ScrolledWindow()

    win.add(port)
    port.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
    tv = gtk.TextView()
    port.add(tv)
    tv.set_buffer(db[filename])
    win.show_all()

    gobject.idle_add(lambda: scroll_to_cursor(tv))
    
    def on_finished(win, evt):
        db[filename] = tv.get_buffer()
        serialize_window(win, db.db.storage)
        gtk.main_quit()
        
    win.connect("delete-event", on_finished)

    # ANAL
    
    gtk.main()

if __name__ == '__main__':
    import sys
    main(sys.argv[1])
